--[[ Copyright 2012, Chris Ho                               ]]--
--[[ MIT License (Please read the LICENSE file for details) ]]--

-- The definition for positional data includes:
-- x: horizontal coordinate
-- y: vertical coordinate
Position = {};

-- Create a position from x/y coordinates.
function Position.Create(_x, _y)
  local instance = { x = _x; y = _y; };

  --[[ Convert the position to individual x/y values. ]]--
  function instance:Decode()
    return self.x, self.y;
  end

  --[[ Calculate desired destination ]]--
  -- Desired destination is at a given range of the original destination.
  function instance:Route(_destination, _range)
    -- If we're too close, then just return the destination.
    if (_range < 1) then
      Debug("Closing in: " .. _range);
      return Position.Create(_destination.x, _destination.y);
    end

    -- We will scale the unit vector to determine the desired position.
    -- The desired position is a given range from the destination. So we
    -- calculate a vector starting from the destination.
    local distance = Position.Measure(_destination, self);
    local delta = Position.Create(self.x - _destination.x, self.y - _destination.y);
    if (distance > 0) then
      -- Calculate position offset.
      delta.x = _range * delta.x / distance;
      delta.y = _range * delta.y / distance;
    else
      -- If we're on top of the enemy, pick a direction.
      local direction = random(1, 4);
      if (direction == 1) then delta.x = _range
      elseif (direction == 2) then delta.y = _range
      elseif (direction == 3) then delta.x = -_range
      elseif (direction == 4) then delta.y = -_range end
    end

    -- Calculate the coordinate translation to the destination. Note
    -- that we add 0.5 to round out the fractions properly.
    return Position.Create(_destination.x + math.floor(delta.x + 0.5), _destination.y + math.floor(delta.y + 0.5));
  end

  --[[ Offset the current position in a random direction ]]--
  -- Note: based on 8-directional movement
  function instance:Disperse(_range)
    if ((_range > 0) and (_range < VISIBILITY)) then
      local diagonal = math.floor(_range * math.sqrt(2) / 2 + 0.5);
      local direction = math.random(1, 8);
      -- Straight
      if (direction == 1) then self.x = self.x + _range
      elseif (direction == 2) then self.y = self.y + _range
      elseif (direction == 3) then self.x = self.x - _range
      elseif (direction == 4) then self.y = self.y - _range
      -- Diagonal
      elseif (direction == 5) then self.x, self.y = self.x + diagonal, self.y + diagonal
      elseif (direction == 6) then self.x, self.y = self.x - diagonal, self.y + diagonal
      elseif (direction == 7) then self.x, self.y = self.x - diagonal, self.y - diagonal
      elseif (direction == 8) then self.x, self.y = self.x + diagonal, self.y - diagonal end
    else
      Alert("Invalid Position Perturb Range: " .. _range);
    end
  end

  return instance;
end

--[[ Find the position of a game piece. ]]--
function Position.Find(_id)
  return Position.Create(GetV(V_POSITION, _id));
end

--[[ Measure integer distance between two positions. ]]--
-- Note that we take the floor value. As a result, the actual
-- distance may be slightly longer. However, when trying to move
-- position two objects to be in range, computing a shorter than
-- actual distance is a lot "safer".
function Position.Measure(_from, _to)
  return math.floor(math.sqrt((_to.x - _from.x) ^ 2 + (_to.y - _from.y) ^ 2));
end
